<?php

namespace wicked\process;

use wicked\core\Request;
use wicked\core\Response;
use wicked\core\Event;

class Router extends Runnable
{

    /** @var array */
    protected static $rules = [];

    /** @var array */
    protected static $table = [];

    /** @var array */
    protected static $conventions = [
        'controller' => '[a-zA-Z0-9-_]+',
        'action' => '[a-zA-Z0-9-_]+',
        'args' => '[\\w]*'
    ];


    /**
     * Set convention regex
     * @static
     * @param $name
     * @param $pattern
     */
    public static function convention($name, $pattern)
    {
        static::$conventions[$name] = $pattern;
    }


    /**
     * Set routing rule
     * @static
     * @param $pattern
     * @param null $callback
     */
    public static function set($pattern, $callback = null)
    {
        // build regex
        foreach(static::$conventions as $name => $regex)
            $pattern = str_replace('(:'.$name.')', '(?<'.$name.'>'.$regex.')', $pattern);
        $pattern = '/' . str_replace('/', '(\/)?', $pattern) . '/';

        // add rule
        static::$rules[$pattern] = $callback;
    }


    /**
     * Securize method
     * @static
     * @param $target
     * @param $rank
     */
    public static function securize($target, $rank)
    {
        static::$table[$target] = $rank;
    }


    /**
     * @static
     * @param $controller
     * @param $method
     * @return bool
     */
    public static function hasAccess($controller, $method)
    {
        $general = array_key_exists($controller, static::$table) and (static::$table[$controller] > Session::rank());
        $local = array_key_exists($controller.'::'.$method, static::$table) and (static::$table[$controller.'::'.$method] > Session::rank());
        return ($general or $local) ? false : true;
    }

    public static function callback($out)
    {
        $controller = !empty($out['controller']) ? $out['controller'] : config('default::controller');
        $action = !empty($out['action']) ? $out['action'] : 'index';
        $args = !empty($out['args']) ? explode('/', $out['args']) : [];

        Response::controller($controller);
        Response::action($action);

        foreach($args as $key => $arg)
            Response::args($key, $arg);
    }


    public function run()
    {
        Event::fire('router.start');

        // 404 event
        if(!Event::has(404))
        {
            Event::listen(404, function(){
                die('404 : please override the 404 event.');
            });
        }

        // 403 event
        if(!Event::has(403))
        {
            Event::listen(403, function(){
                die('403 : please override the 403 event.');
            });
        }

        // default route
        if(empty(static::$rules))
            static::set(config('default::route'));

        // get query string
        $uri = Request::uri();

        // routing
        $match = false;
        foreach(static::$rules as $pattern => $callback)
        {
            if(preg_match($pattern, $uri, $out))
            {
                $match = true;
                if(is_null($callback) or !is_callable($callback))
                    static::callback($out);
                else
                    $callback($out);

                // 403
                if(!static::hasAccess(Response::controller(), Response::action()))
                    Event::fire(403);
            }
        }

        // 404
        if(!$match)
            Event::fire(404);

        Event::fire('router.end');
    }
}
